# frozen_string_literal: true

require('fox16')
require('fox16/canvas')
require('./lib')

include Fox
include Canvas

require_relative('gui-board')

class KlotskiGui < FXMainWindow
  class GuiMenuBar < FXMenuBar
    attr_reader(
      :widgets
    )

    def initialize(main, observable)
      super(main, LAYOUT_SIDE_TOP | LAYOUT_FILL_X)
      @widgets = {}
      FXMenuPane.new(main).tap do |menu|
        FXMenuCommand.new(
          menu,
          "&Quit\tCtl-Q\tQuit the application.",
          nil,
          main.getApp,
          FXApp::ID_QUIT
        )
        FXMenuTitle.new(self, '&File', nil, menu)
      end

      @widgets[:game_select] = Class.new(FXMenuPane) do
        attr_reader(:observable)

        def initialize(main, observable)
          super(main)
          @observable = observable
        end
      end.new(main, observable).tap do |menu|
        Game.games.each do |game|
          FXMenuRadio.new(menu, game.to_s).tap do |r|
            r.connect(SEL_COMMAND) do
              observable.dispatch_event(:new_game, game)
            end
            r.connect(SEL_UPDATE) do |sender, _sel, _ptr|
              sender.check = (Game.instance.game == game)
            end
          end
        end
        FXMenuTitle.new(self, 'New &Game', nil, menu)
      end
    end
  end

  def initialize(app, size_factor)
    super(app, 'Klotski', nil, nil, DECOR_ALL)
    @widgets = {}

    @widgets[:game_select] = GuiMenuBar.new(self, Observable.new).widgets[:game_select]
    FXVerticalFrame.new(self, LAYOUT_CENTER_X | LAYOUT_CENTER_Y).tap do |v_frame|
      opts_gb = FRAME_THICK | GROUPBOX_TITLE_CENTER
      FXGroupBox.new(v_frame, nil, opts_gb).tap do |frame|
        %i[padTop= padBottom= padLeft= padRight=].each do |prop|
          frame.send(prop, DEFAULT_PAD * 4)
        end
        @widgets[:board] = GuiBoard.nu_for_board(frame, size_factor)
      end

      opts_hf  = FRAME_THICK | LAYOUT_FILL_X
      opts_btn = BUTTON_NORMAL | LAYOUT_FILL_X
      FXHorizontalFrame.new(v_frame, opts_hf).tap do |h_frame|
        @widgets[:solve] = FXButton.new(h_frame, 'solve', :opts => opts_btn)
      end

      FXHorizontalFrame.new(v_frame, opts_hf).tap do |h_frame|
        @widgets[:undo] = FXButton.new(h_frame, 'undo', :opts => opts_btn)
        @widgets[:next] = FXButton.new(h_frame, 'next', :opts => opts_btn)
      end

      FXHorizontalFrame.new(v_frame, opts_hf).tap do |h_frame|
        @widgets[:reset] = FXButton.new(h_frame, 'reset', :opts => opts_btn)
      end

      opts_lbl = LABEL_NORMAL | LAYOUT_FILL_X
      FXHorizontalFrame.new(v_frame, LAYOUT_FILL_X).tap do |h_frame|
        @widgets[:status] = FXLabel.new(h_frame, '...', :opts => opts_lbl)
      end

      FXHorizontalFrame.new(v_frame, LAYOUT_FILL_X).tap do |h_frame|
        @widgets[:solve_details] = FXLabel.new(h_frame, '---', :opts => opts_lbl)
      end
    end
    show(PLACEMENT_SCREEN)
  end

  def observable(id, &block)
    return (
      case @widgets[id]
      when FXButton     then ButtonObservable.new(@widgets[id])
      when FXLabel      then LabelObservable.new(@widgets[id])
      when GuiBoard     then BoardObservable.new(@widgets[id])
      else @widgets[id]&.observable if @widgets[id]
      end.tap do |o|
        raise "cannnot bind to widget of type #{@widgets[id].class1}" unless o

        block&.call(o)
      end
    )
  end

  def task_agent
    return TaskAgent.new(getApp)
  end

  class TaskAgent
    def initialize(app)
      @app = app
    end

    def add_repetitive_task(&block)
      @app.addChore do
        add_repetitive_task(&block) if block.call
      end
    end
  end

  class LabelObservable < Observable
    def initialize(fx_object)
      super()
      @fx_object = fx_object
    end

    def [](name)
      case name
      when :enabled then @fx_object.enabled?
      when :text    then @fx_object.text
      else super
      end
    end

    def []=(name, value)
      @fx_object.runOnUiThread do
        case name
        when :enabled then @fx_object.enabled = value
        when :text    then @fx_object.text = value
        else super
        end
      end
    end
  end

  class ButtonObservable < LabelObservable
    def initialize(fx_object)
      super
      fx_object.connect(SEL_COMMAND) {dispatch_event}
    end
  end
end
